/**
 * @author Kevin Gomes
 */

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.sql.*;
import java.util.*;

import utils.*; //utils written for this project. Has MysqlConnect.java

/**
 * Class: Client
 *
 * Each Peer acts as a Client as well as Server. This class handles the Client role of a Peer.
 * This class would be used by the Peer class.
 * In addition, this class includes the main() method that demonstrates how this class can be used.
 */
public class Client {
    /**
     * Peer IP Address.
     */
    private String peer_ip;

    /**
     * Peer name.
     */
    private String peer_name;

    /**
     * DB Connection.
     */
    private MysqlConnect conn;

    //Variable to het the list of ips
    private String [] myNeighbours;

    //Array of socket objects
    private ArrayList<Socket> peerSocket;

    //Descriptor IDs
    private String[] descriptor; 
    
    //Output Stream Object for Sockets
    private ArrayList<PrintWriter> output;

    //Input Stream Object for Sockets
    private ArrayList<BufferedReader> input;
    
    private BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));

    private Server serverObj;

    /**
     * Constructor.
     */
    public Client() { 
	this.peerSocket = new ArrayList();
	this.output = new ArrayList();
	this.input = new ArrayList();
    }

    public void init(String peer_name, Server serverObj) {
	//Save peer ip address
	this.setPeerIp(this.getIp());

	//Save peer name
	this.setPeerName(peer_name);

	//Setup a database connection
        this.conn = new MysqlConnect();

	
	System.out.println("Peer IP:"+this.peer_ip);
	// List of Neighbors
        this.myNeighbours = new ConfigParser().getNeighbors(this.peer_ip);
	System.out.println(this.myNeighbours[0]);

	//save file list to the database.
        this.updateAvailableFiles();

	this.serverObj = serverObj;
        
        //this.connectNeighbors();
    }

    /**
     * Getters and Setters
     */
    public void setPeerName(String value) { this.peer_name = value; }
    public String getPeerName() { return this.peer_name; } 
    public void setPeerIp(String value) { this.peer_ip = value; }
    public String getPeerIp(String value) { return this.peer_ip; }

    /**
     * Returns the IP Address of the machine this client is running on.
     */
    public String getIp(){
        String ip=new String ();
        try{
            InetAddress ownIP = InetAddress.getLocalHost();
            ip = ownIP.getHostAddress();
        }catch (Exception e){
            System.out.println("Exception caught ="+e.getMessage());
        }
        return ip;
    }
    
    /**
     * Updates the list of available files with the peer.
     * The list of available files are stored in mysql databases.
     * Ideally, each peer would have a local instance of its DB.
     * But, in this case all peers use a shared database.
     */
    public void updateAvailableFiles() {
	ArrayList files = this.getFilesInDir();
	//System.out.println("All Files: " + files.toString());

	try {
	    this.conn.insertInPeerFiles(this.peer_ip, this.peer_name, files);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

    /**
     * Returns an array of files on the Client.
     */
    public ArrayList getFilesInDir() {
        ArrayList files = new ArrayList();

        try {
            File dir = new File("./");
            String[] children = dir.list();

            for(int i = 0;i < children.length;i++) {
                files.add(children[i]);
            }
        } catch (Exception e) {
            System.out.println(e);
        }

        return files;
    }
    /**
     * Returns the query descriptor.
     */
    public String createQueryDescriptor(String ttl, String searchFilename) {
        UUID val = UUID.randomUUID();
        String descriptorId = String.valueOf(val);
        String payloadDescriptor = "128";
        String hop = "0";
        String payloadLength = "0";

        String speed = "0";

	String descriptor = descriptorId+";"+payloadDescriptor+";"+ttl+";"+hop+";"+payloadLength+";"+speed+";"+searchFilename+";NULL";
        return descriptor;
    }

    /**
     *
     */
    public String fetchDescriptorId(String descriptor) {
        String tokens[] = descriptor.split(",");
        return tokens[0];
    }
    
    public void connectNeighbors() {
        for (int i = 0; i< myNeighbours.length;i++){
	    System.out.println("Preparing to connect to: "+myNeighbours[i]);
	    try {
		//Creates Socket with all neighbouring Peer Servers
		//peerSocket[i] = new Socket(myNeighbours[i],4444);

		//this.peerSocket.add(new Socket("216.47.152.98",4444));
		this.peerSocket.add(new Socket(myNeighbours[i],6000));

		//Creates Sockets Output Stream        
		this.output.add(new PrintWriter(peerSocket.get(i).getOutputStream(), true));
		
		//Creates Socket Input Stream
		this.input.add(new BufferedReader(new InputStreamReader(peerSocket.get(i).getInputStream())));
		
		//ClientThread thread = new ClientThread(peerSocket.get(i), this);
		//thread.start();
	    } catch (UnknownHostException e) {
		System.err.println("Don't know about host");
		e.printStackTrace();
		System.exit(1);
	    } catch (IOException e) {
		System.err.println("Couldn't get I/O for the connection .");
		e.printStackTrace();
		System.exit(1);
	    }
        }
    }
    
    public void talkToNeighbor (String descriptor) {
	System.out.println("Talk to neighbor: "+descriptor);
        String fromUser = descriptor;
        if (fromUser != null) {
            for (int i = 0; i< myNeighbours.length;i++) {
		output.get(i).println(fromUser);
	    }
	}
    }

    public void searchNeighbor(String fileName) {
	String searchFile = fileName ;
	String descriptor = this.createQueryDescriptor("3",searchFile);
        //Forwarding the querry to all neighbousa
        this.talkToNeighbor(descriptor);
    }

    public String reduceDescriptorTTL(String descriptor) {
	String returnString="";
	String[] tokens= descriptor.split(",");
	int ttl = new Integer (tokens[2]);
	ttl--;
	tokens[2] = Integer.toString(ttl);
	int hop = new Integer (tokens[3]);
	hop++;
	tokens[3] = Integer.toString(hop);
	if (tokens.length>0){
	    returnString = tokens[0];
	    for(int i = 1 ;i<tokens.length;i++ ) {
		returnString = returnString+","+tokens[i];
	    }
        }
	return returnString;
    }

    public void closeConnections () {
	try{
	    stdIn.close();

	    if(this.peerSocket.isEmpty() == false) {
		System.out.println("Peer Socket: "+this.peerSocket.size());
		
		for(int i=0;i<myNeighbours.length;i++) {
		    output.get(i).close();
		    input.get(i).close();
		    peerSocket.get(i).close();
		}	    }
        } catch(IOException e) {
	    System.err.println("Uff Not able to close the connections");
        }
    }
    
    /**
     * This function processes the QueryHit descriptors sent by other servers.
     */
    public void processQueryHit(String descriptor, String remoteIp) {
	//Accept QueryHit descriptors
	//Store the descriptor ID
	//Forward the descriptor to the Client so that it can be sent to the neighbors.
	
	String tokens[] = descriptor.split(",");
	String descriptorId = tokens[0];
	String payloadDescriptor = tokens[1];
	int ttl = new Integer(tokens[2]);
	int hop = new Integer(tokens[3]);
	
	if(ttl > 0) {
	    //Forward the QueryHit to neighboring peers
	    this.serverObj.processQueryHitDescriptor(descriptor);
	}

	//Need to store all the peers that have sent the QueryHit in an array
	System.out.println("Peer Found: "+remoteIp);
    }
}

